/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "UpnpFrame.h"
#include "UpnpTreeItemData.h"
#include "SoapFrame.h"
#include "GenaFrame.h"

// begin wxGlade: ::extracode

// end wxGlade

struct get_context {
    UpnpFrame *frame;
    struct tansu_cp_discovery *discovery;
    char *sid;
};

static void
dd_handler(struct evhttp_request *req, void *arg)
{
    struct get_context *context = (struct get_context *)arg;
    UpnpFrame *frame = context->frame;
    struct tansu_cp_discovery *discovery = context->discovery;
    delete context;
    frame->DDHandler(req, discovery);
}

static void
scpd_handler(struct evhttp_request *req, void *arg)
{
    struct get_context *context = (struct get_context *)arg;
    UpnpFrame *frame = context->frame;
    struct tansu_cp_discovery *discovery = context->discovery;
    frame->SCPDHandler(req, discovery, context->sid);
    free(context->sid);
    delete context;
}

static void
ssdp_callback(const struct tansu_ssdp_packet *packet, void *arg)
{
    ((UpnpFrame*)arg)->HandlePacket(packet);
}

static void
ssdp_response(struct tansu_ssdp_packet *packet, void *arg)
{
    ((UpnpFrame*)arg)->HandlePacket(packet);
}

static void
msearch_finished(struct tansu_ssdp_request *req, void *arg)
{
    ((UpnpFrame*)arg)->MSearchFinished(req);
}


UpnpFrame::UpnpFrame(struct event_base* evbase,
                     wxWindow* parent,
                     int id,
                     const wxString& title,
                     const wxPoint& pos,
                     const wxSize& size,
                     long style)
    : wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE)
    , server(NULL)
    , listener(NULL)
    , num_responses(0)
    , num_valid(0)
    , filter(_(""))
    , evbase(evbase)
{
    // begin wxGlade: UpnpFrame::UpnpFrame
    window_1 = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_3D|wxSP_BORDER);
    panel_2 = new wxPanel(window_1, wxID_ANY);
    panel_3 = new wxPanel(panel_2, wxID_ANY);
    notebook_2 = new wxNotebook(panel_2, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
    notebook_2_pane_3 = new wxPanel(notebook_2, wxID_ANY);
    notebook_2_pane_2 = new wxPanel(notebook_2, wxID_ANY);
    notebook_2_pane_1 = new wxPanel(notebook_2, wxID_ANY);
    notebook_1 = new wxNotebook(panel_2, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
    notebook_1_pane_2 = new wxPanel(notebook_1, wxID_ANY);
    notebook_1_pane_1 = new wxPanel(notebook_1, wxID_ANY);
    panel_1 = new wxPanel(window_1, wxID_ANY);
    notebook_tree = new wxNotebook(panel_1, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
    frame_1_statusbar = CreateStatusBar(2, 0);
    const wxString combo_box_st_choices[] = {
        wxT("ssdp:all"),
        wxT("upnp:rootdevice"),
        wxT("urn:schemas-upnp-org:device:"),
        wxT("urn:schemas-upnp-org:service:"),
        wxT("urn:schemas-upnp-org:device:Basic:1"),
        wxT("urn:schemas-upnp-org:device:MediaServer:1"),
        wxT("urn:schemas-upnp-org:device:MediaRenderer:1"),
        wxT("urn:schemas-upnp-org:service:ContentDirectory:1"),
        wxT("urn:schemas-upnp-org:service:ConnectionManager:1"),
        wxT("urn:schemas-upnp-org:service:AVTransport:1"),
        wxT("urn:schemas-upnp-org:service:RenderingControl:1")
    };
    combo_box_st = new wxComboBox(this, ID_SEARCH_TARGET, wxT(""), wxDefaultPosition, wxDefaultSize, 11, combo_box_st_choices, wxCB_DROPDOWN);
    button_search = new wxButton(this, ID_MSEARCH_BUTTON, wxT("M-SEARCH"));
    const wxString combo_box_filter_choices[] = {
        wxEmptyString,
        wxT("urn:schemas-upnp-org:device:"),
        wxT("urn:schemas-upnp-org:service:"),
        wxT("urn:schemas-upnp-org:device:MediaServer:"),
        wxT("urn:schemas-upnp-org:device:MediaRenderer:"),
        wxT("urn:schemas-upnp-org:device:Basic:"),
        wxT("uuid:")
    };
    combo_box_filter = new wxComboBox(this, ID_FILTER, wxT(""), wxDefaultPosition, wxDefaultSize, 7, combo_box_filter_choices, wxCB_DROPDOWN);
    button_start = new wxToggleButton(this, ID_START_BUTTON, wxT("&Start"));
    tree_ctrl_1 = new wxTreeCtrl(notebook_tree, ID_TREE_CTRL, wxDefaultPosition, wxDefaultSize, wxTR_HAS_BUTTONS|wxTR_NO_LINES|wxTR_HIDE_ROOT|wxTR_DEFAULT_STYLE|wxSUNKEN_BORDER);
    list_ctrl_ssdp = new wxListCtrl(notebook_tree, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSUNKEN_BORDER);
    button_collapse = new wxButton(panel_1, ID_COLLAPSE_BUTTON, wxT("Collapse"));
    button_expand = new wxButton(panel_1, ID_EXPAND_BUTTON, wxT("Expand"));
    button_clear = new wxButton(panel_1, ID_CLEAR_BUTTON, wxT("Clear"));
    label_15 = new wxStaticText(notebook_1_pane_1, wxID_ANY, wxT("Location"));
    text_ctrl_location = new wxTextCtrl(notebook_1_pane_1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    label_1 = new wxStaticText(notebook_1_pane_1, wxID_ANY, wxT("major"));
    text_ctrl_device_major = new wxTextCtrl(notebook_1_pane_1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    label_2 = new wxStaticText(notebook_1_pane_1, wxID_ANY, wxT("minor"));
    text_ctrl_device_minor = new wxTextCtrl(notebook_1_pane_1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    label_3 = new wxStaticText(notebook_1_pane_1, wxID_ANY, wxT("URLBase"));
    text_ctrl_url_base = new wxTextCtrl(notebook_1_pane_1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    list_ctrl_device = new wxListCtrl(notebook_1_pane_1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSUNKEN_BORDER);
    label_4 = new wxStaticText(notebook_1_pane_1, wxID_ANY, wxT("iconList"));
    list_ctrl_icon = new wxListCtrl(notebook_1_pane_1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSUNKEN_BORDER);
    label_5 = new wxStaticText(notebook_1_pane_1, wxID_ANY, wxT("serviceList"));
    list_ctrl_service = new wxListCtrl(notebook_1_pane_1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSUNKEN_BORDER);
    label_6 = new wxStaticText(notebook_1_pane_1, wxID_ANY, wxT("presentationURL"));
    text_ctrl_presentation = new wxTextCtrl(notebook_1_pane_1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    text_ctrl_dd = new wxTextCtrl(notebook_1_pane_2, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxHSCROLL|wxTE_AUTO_URL);
    label_7 = new wxStaticText(notebook_2_pane_1, wxID_ANY, wxT("major"));
    text_ctrl_service_major = new wxTextCtrl(notebook_2_pane_1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    label_8 = new wxStaticText(notebook_2_pane_1, wxID_ANY, wxT("minor"));
    text_ctrl_service_minor = new wxTextCtrl(notebook_2_pane_1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    list_ctrl_action = new wxListCtrl(notebook_2_pane_1, ID_ACTION_LIST_CTRL, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_NO_HEADER|wxLC_SINGLE_SEL|wxSUNKEN_BORDER);
    button_exec = new wxButton(notebook_2_pane_1, ID_EXEC_BUTTON, wxT("Exec"));
    button_exec_option = new wxButton(notebook_2_pane_1, ID_EXEC_OPTION_BUTTON, wxT("Exec (Option)"));
    label_9 = new wxStaticText(notebook_2_pane_1, wxID_ANY, wxT("argumentList"));
    list_ctrl_argument = new wxListCtrl(notebook_2_pane_1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSUNKEN_BORDER);
    list_ctrl_state_variable = new wxListCtrl(notebook_2_pane_2, ID_STATE_VARIABLE_LIST_CTRL, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_SINGLE_SEL|wxSUNKEN_BORDER);
    button_subscribe = new wxButton(notebook_2_pane_2, ID_SUBSCRIBE_BUTTON, wxT("Watch"));
    label_10 = new wxStaticText(notebook_2_pane_2, wxID_ANY, wxT("allowedValueList"));
    list_ctrl_allowed_value = new wxListCtrl(notebook_2_pane_2, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_NO_HEADER|wxSUNKEN_BORDER);
    label_11 = new wxStaticText(notebook_2_pane_2, wxID_ANY, wxT("minimum"));
    text_ctrl_minimum = new wxTextCtrl(notebook_2_pane_2, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    label_12 = new wxStaticText(notebook_2_pane_2, wxID_ANY, wxT("maximum"));
    text_ctrl_maximum = new wxTextCtrl(notebook_2_pane_2, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    label_13 = new wxStaticText(notebook_2_pane_2, wxID_ANY, wxT("step"));
    text_ctrl_step = new wxTextCtrl(notebook_2_pane_2, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
    text_ctrl_scpd = new wxTextCtrl(notebook_2_pane_3, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxHSCROLL|wxTE_AUTO_URL);
    label_14 = new wxStaticText(panel_3, wxID_ANY, wxT("No device/service selected"));

    set_properties();
    do_layout();
    // end wxGlade

    root = tree_ctrl_1->AddRoot(_("Devices"));

    STAILQ_INIT(&discoveries);

    server = tansu_ssdp_server_new();
    if (server == NULL)
        return;

    server->server_name = strdup("Linux/2.6, UPnP/1.0, noname/0.1");
    listener = tansu_ssdp_server_add(server, ssdp_callback, this);

    req = tansu_ssdp_request_new("ssdp:all");
    req->arena = &server->arena;
    req->mcast_addr = &server->mcast_addr;
    req->cb = ssdp_response;
    req->cbarg = this;
    req->finish_cb = msearch_finished;
    req->finish_cbarg = this;
}


UpnpFrame::~UpnpFrame()
{
    struct tansu_cp_discovery *discovery;

    STAILQ_FREE_ALL(discovery, &discoveries, next, tansu_cp_discovery_delete);

    tansu_ssdp_server_remove(server, listener);
    tansu_ssdp_server_stop(server);
    tansu_ssdp_server_delete(server);
    tansu_ssdp_request_delete(req);
}


BEGIN_EVENT_TABLE(UpnpFrame, wxFrame)
    // begin wxGlade: UpnpFrame::event_table
    EVT_TEXT_ENTER(ID_SEARCH_TARGET, UpnpFrame::OnSearchTargetTextEnter)
    EVT_BUTTON(ID_MSEARCH_BUTTON, UpnpFrame::OnMSearchButton)
    EVT_TEXT_ENTER(ID_FILTER, UpnpFrame::OnFilterTextEnter)
    EVT_COMBOBOX(ID_FILTER, UpnpFrame::OnFilterComboBox)
    EVT_TOGGLEBUTTON(ID_START_BUTTON, UpnpFrame::OnToggleButton)
    EVT_TREE_SEL_CHANGED(ID_TREE_CTRL, UpnpFrame::OnTreeSelChanged)
    EVT_BUTTON(ID_COLLAPSE_BUTTON, UpnpFrame::OnCollapseButton)
    EVT_BUTTON(ID_EXPAND_BUTTON, UpnpFrame::OnExpandButton)
    EVT_BUTTON(ID_CLEAR_BUTTON, UpnpFrame::OnClearButton)
    EVT_LIST_ITEM_DESELECTED(ID_ACTION_LIST_CTRL, UpnpFrame::OnActionDeselected)
    EVT_LIST_ITEM_SELECTED(ID_ACTION_LIST_CTRL, UpnpFrame::OnActionSelected)
    EVT_BUTTON(ID_EXEC_BUTTON, UpnpFrame::OnActionExecButton)
    EVT_BUTTON(ID_EXEC_OPTION_BUTTON, UpnpFrame::OnActionExecOptionButton)
    EVT_LIST_ITEM_DESELECTED(ID_STATE_VARIABLE_LIST_CTRL, UpnpFrame::OnStateVariableDeselected)
    EVT_LIST_ITEM_SELECTED(ID_STATE_VARIABLE_LIST_CTRL, UpnpFrame::OnStateVariableSelected)
    EVT_BUTTON(ID_SUBSCRIBE_BUTTON, UpnpFrame::OnSubscribeButton)
    // end wxGlade
END_EVENT_TABLE();


void UpnpFrame::OnSearchTargetTextEnter(wxCommandEvent &event)
{
    event.Skip();
    ExecuteMSearch();
}


void UpnpFrame::OnMSearchButton(wxCommandEvent &event)
{
    event.Skip();
    ExecuteMSearch();
}


void UpnpFrame::OnFilterTextEnter(wxCommandEvent &event)
{
    event.Skip();
    filter = combo_box_filter->GetValue();
}


void UpnpFrame::OnFilterComboBox(wxCommandEvent &event)
{
    event.Skip();
    filter = combo_box_filter->GetValue();
}


void UpnpFrame::OnToggleButton(wxCommandEvent &event)
{
    event.Skip();

    if (button_start->GetValue() == false) {
        tansu_ssdp_server_stop(server);
        button_start->SetLabel(_("&Start"));
        frame_1_statusbar->SetStatusText(_("stopped"), 1);
        return;
    }

    if (server == NULL)
        return;

    struct tansu_nwifs *nwifs = tansu_nwif_getaddrs(NULL);
    if (nwifs == NULL)
        return;

    tansu_status_t status = tansu_ssdp_server_start(evbase, server, nwifs);
    if (status == TANSU_SUCCESS) {
        button_start->SetLabel(_("&Stop"));
        frame_1_statusbar->SetStatusText(_("started"), 1);
    } else {
        printf("tansu_ssdp_server_start() failed %d\n", status);
    }

    tansu_nwif_freeaddrs(nwifs);
}


void UpnpFrame::SetDeviceParam(struct tdom_elem *elem)
{
    const char *tmp;
    int n = list_ctrl_device->GetItemCount();
    tmp = elem->node.name;
    long m = list_ctrl_device->InsertItem(n, wxString::FromUTF8(tmp));
    if ((tmp = tdom_get_value(elem)))
        list_ctrl_device->SetItem(m, 1, wxString::FromUTF8(tmp));
}

void UpnpFrame::SetIconList(struct tdom_elem *elem)
{
    struct tdom_elem *icon;
    int n = tdom_count_elem(elem, "icon", TANSU_NS_UPNP_DEVICE);
    int i;
    uint32_t j;
    const char *tmp;

    for (i = 0; i < n; i++) {
        icon = tdom_get_elem(elem, "icon", TANSU_NS_UPNP_DEVICE, i);
        if (icon == NULL)
            continue;
        tmp = tdom_get_value(tdom_get_elem(icon, "mimetype", TANSU_NS_UPNP_DEVICE, 0));
        if (tmp == NULL)
            continue;
        long m = list_ctrl_icon->GetItemCount();
        m = list_ctrl_icon->InsertItem(m, wxString::FromAscii(tmp));
        const char *list[] = {"width", "height", "depth", "url"};
        for (j = 0; j < TNUMELEM(list); j++) {
            tmp = tdom_get_value(tdom_get_elem(icon, list[j], TANSU_NS_UPNP_DEVICE, 0));
            if (tmp)
                list_ctrl_icon->SetItem(m, j + 1, wxString::FromAscii(tmp));
        }
    }
}

void UpnpFrame::SetServiceList(struct tdom_elem *elem)
{
    struct tdom_elem *service;
    int i;
    uint32_t j;
    const char *tmp;
    int n = tdom_count_elem(elem, "service", TANSU_NS_UPNP_DEVICE);

    for (i = 0; i < n; i++) {
        service = tdom_get_elem(elem, "service", TANSU_NS_UPNP_DEVICE, i);
        if (service == NULL)
            continue;
        tmp = tdom_get_value(tdom_get_elem(service, "serviceType", TANSU_NS_UPNP_DEVICE, 0));
        if (tmp == NULL)
            continue;
        long m = list_ctrl_service->GetItemCount();
        m = list_ctrl_service->InsertItem(m, wxString::FromAscii(tmp));
        const char *list[] = {"serviceId", "SCPDURL", "controlURL", "eventSubURL"};
        for (j = 0; j < TNUMELEM(list); j++) {
            tmp = tdom_get_value(tdom_get_elem(service, list[j], TANSU_NS_UPNP_DEVICE, 0));
            if (tmp)
                list_ctrl_service->SetItem(m, j + 1, wxString::FromAscii(tmp));
        }
    }
}


void UpnpFrame::SetDeviceInfo(DeviceTreeItemData *data)
{
    text_ctrl_location->SetValue(wxEmptyString);
    text_ctrl_device_major->SetValue(wxEmptyString);
    text_ctrl_device_minor->SetValue(wxEmptyString);
    text_ctrl_url_base->SetValue(wxEmptyString);
    text_ctrl_dd->SetValue(wxEmptyString);
    text_ctrl_presentation->SetValue(wxEmptyString);
    list_ctrl_device->DeleteAllItems();
    list_ctrl_icon->DeleteAllItems();
    list_ctrl_service->DeleteAllItems();

    if (data == NULL)
        return;

    const struct tansu_cp_device *cp_device = data->GetDevice();
    struct tdom_elem *elem;
    const char *tmp;
    wxString str;
    uint32_t i;

    text_ctrl_location->SetValue(wxString::FromAscii(cp_device->location));

    elem = tdom_get_elem(cp_device->dd->root, "specVersion", TANSU_NS_UPNP_DEVICE, 0);

    struct {
        struct tdom_elem *elem;
        const char *name;
        wxTextCtrl *text_ctrl;
    } list[] = {
        {elem, "major", text_ctrl_device_major},
        {elem, "minor", text_ctrl_device_minor},
        {cp_device->dd->root, "URLBase", text_ctrl_url_base},
    };

    for (i = 0; i < TNUMELEM(list); i++) {
        if (list[i].elem == NULL)
            continue;
        if ((tmp = tdom_get_value(tdom_get_elem(list[i].elem, list[i].name, TANSU_NS_UPNP_DEVICE, 0))))
            list[i].text_ctrl->SetValue(wxString::FromUTF8(tmp));
    }

    elem = cp_device->embedded ? cp_device->embedded : cp_device->rootdevice;

    if (elem == NULL)
        return;

    struct tdom_node *node;
    TAILQ_FOREACH(node, &elem->children, sibling) {
        if (node->type != TDOM_ELEM)
            continue;
        if ((tmp = strchr(node->name, ':')))
            tmp = &tmp[1];
        else
            tmp = node->name;
        if (strcmp("iconList", tmp) == 0)
            SetIconList((struct tdom_elem *)node);
        else if (strcmp("serviceList", tmp) == 0)
            SetServiceList((struct tdom_elem *)node);
        else if (strcmp("deviceList", tmp) == 0)
            continue;
        else
            SetDeviceParam((struct tdom_elem *)node);
    }

    tmp = tdom_get_value(tdom_get_elem(elem, "presentationURL", TANSU_NS_UPNP_DEVICE, 0));
    if (tmp)
        text_ctrl_presentation->SetValue(wxString::FromUTF8(tmp));

    SoapFrame::SetXML(*text_ctrl_dd, cp_device->dd, NULL);
}


void UpnpFrame::SetServiceInfo(ServiceTreeItemData *data)
{
    text_ctrl_service_major->SetValue(wxEmptyString);
    text_ctrl_service_minor->SetValue(wxEmptyString);
    text_ctrl_minimum->SetValue(wxEmptyString);
    text_ctrl_maximum->SetValue(wxEmptyString);
    text_ctrl_step->SetValue(wxEmptyString);
    text_ctrl_scpd->SetValue(wxEmptyString);
    list_ctrl_action->DeleteAllItems();
    list_ctrl_argument->DeleteAllItems();
    list_ctrl_state_variable->DeleteAllItems();
    list_ctrl_allowed_value->DeleteAllItems();

    if (data == NULL || data->GetService() == NULL)
        return;

    const struct tansu_cp_service *cp_service = data->GetService();
    struct tdom_elem *elem;
    const char *tmp;
    wxString str;
    int i;
    int n;
    int m;

    // specVersion
    elem = tdom_get_elem(cp_service->scpd->root, "specVersion", TANSU_NS_UPNP_SERVICE, 0);

    struct {
        struct tdom_elem *elem;
        const char *name;
        const char *ns;
        wxTextCtrl *text_ctrl;
    } list[] = {
        {elem, "major", TANSU_NS_UPNP_SERVICE, text_ctrl_service_major},
        {elem, "minor", TANSU_NS_UPNP_SERVICE, text_ctrl_service_minor},
    };

    for (i = 0; i < (int)TNUMELEM(list); i++) {
        if (list[i].elem == NULL)
            continue;
        if ((tmp = tdom_get_value(tdom_get_elem(list[i].elem, list[i].name, list[i].ns, 0))))
            list[i].text_ctrl->SetValue(wxString::FromUTF8(tmp));
    }

    // action
    elem = tdom_get_elem(cp_service->scpd->root, "actionList", TANSU_NS_UPNP_SERVICE, 0);
    if (elem) {
        struct tdom_elem *action;
        n = tdom_count_elem(elem, "action", TANSU_NS_UPNP_SERVICE);
        for (i = 0; i < n; i++) {
            action = tdom_get_elem(elem, "action", TANSU_NS_UPNP_SERVICE, i);
            if (action == NULL)
                continue;
            const char *name;
            name = tdom_get_value(tdom_get_elem(action, "name", TANSU_NS_UPNP_SERVICE, 0));
            if (name) {
                m = list_ctrl_action->GetItemCount();
                list_ctrl_action->InsertItem(m, wxString::FromUTF8(name));
            }
        }
    }

    // state variable
    elem = tdom_get_elem(cp_service->scpd->root, "serviceStateTable", TANSU_NS_UPNP_SERVICE, 0);
    if (elem) {
        struct tdom_elem *var;
        n = tdom_count_elem(elem, "stateVariable", TANSU_NS_UPNP_SERVICE);
        for (i = 0; i < n; i++) {
            var = tdom_get_elem(elem, "stateVariable", TANSU_NS_UPNP_SERVICE, i);
            if (var == NULL)
                continue;
            const char *tmp;

            struct {
                const char *name;
                const char *ns;
                int attr;
                int col;
            } list[] = {
                {"name",            TANSU_NS_UPNP_SERVICE,  0, 0},
                {"sendEvents",      TANSU_NS_UPNP_SERVICE,  1, 1},
                {"dataType",        TANSU_NS_UPNP_SERVICE,  0, 2},
                {"defaultValue",    TANSU_NS_UPNP_SERVICE,  0, 3},
            };
            uint32_t j;
            for (j = 0; j < TNUMELEM(list); j++) {
                if (list[j].attr)
                    tmp = tdom_get_attr(var, list[j].name, list[j].ns);
                else
                    tmp = tdom_get_value(tdom_get_elem(var, list[j].name, list[j].ns, 0));
                if (j == 0) {
                    if (tmp == NULL)
                        break;
                    m = list_ctrl_state_variable->GetItemCount();
                    list_ctrl_state_variable->InsertItem(m, wxString::FromUTF8(tmp));
                } else {
                    if (tmp) {
                        m = list_ctrl_state_variable->GetItemCount() - 1;
                        list_ctrl_state_variable->SetItem(m, list[j].col, wxString::FromUTF8(tmp));
                    }
                }
            }
        }
    }

    // scpd text
    SoapFrame::SetXML(*text_ctrl_scpd, cp_service->scpd, NULL);
}


void UpnpFrame::OnTreeSelChanged(wxTreeEvent &event)
{
    event.Skip();

    wxTreeItemId id = event.GetItem();
    if (tree_ctrl_1->GetItemParent(id) == root) {
        SetActivePanel(PANEL_NONE);
        return;
    }

    UpnpTreeItemData *data = (UpnpTreeItemData *)tree_ctrl_1->GetItemData(id);
    if (data == NULL) {
        SetActivePanel(PANEL_NONE);
        return;
    }

    if (data->GetNodeType() == TREE_NODE_DEVICE) {
        SetDeviceInfo((DeviceTreeItemData *)data);
        SetActivePanel(PANEL_DEVICE);
        return;
    }

    if (data->GetNodeType() == TREE_NODE_SERVICE) {
        SetServiceInfo((ServiceTreeItemData *)data);
        SetActivePanel(PANEL_SERVICE);
    }
}


void UpnpFrame::OnCollapseButton(wxCommandEvent &event)
{
    event.Skip();
    tree_ctrl_1->CollapseAll();
}


void UpnpFrame::OnExpandButton(wxCommandEvent &event)
{
    event.Skip();

    // expand all device types and its children
    //tree_ctrl_1->ExpandAllChildren(root);

    // expand all device types
    wxTreeItemIdValue cookie;
    wxTreeItemId id = tree_ctrl_1->GetFirstChild(root, cookie);
    DeviceTreeItemData *data;

    while (id.IsOk() == true) {
        tree_ctrl_1->Expand(id);
        id = tree_ctrl_1->GetNextSibling(id);
    }
}


void UpnpFrame::OnClearButton(wxCommandEvent &event)
{
    event.Skip();
    int n = notebook_tree->GetSelection();
    if (n == 0) {
        tree_ctrl_1->DeleteChildren(root);
        SetDeviceInfo(NULL);
        SetActivePanel(PANEL_NONE);
    } else {
        struct tansu_cp_discovery *discovery;
        STAILQ_FREE_ALL(discovery, &discoveries, next, tansu_cp_discovery_delete);
        list_ctrl_ssdp->DeleteAllItems();
    }
}


void UpnpFrame::OnActionDeselected(wxListEvent &event)
{
    event.Skip();
    list_ctrl_argument->DeleteAllItems();

    button_exec->Enable(false);
    button_exec_option->Enable(false);
}


void UpnpFrame::OnActionSelected(wxListEvent &event)
{
    event.Skip();

    list_ctrl_argument->DeleteAllItems();

    long idx = event.GetIndex();
    wxString str = list_ctrl_action->GetItemText(idx);

    wxTreeItemId item = tree_ctrl_1->GetSelection();
    if (item.IsOk() == false)
        return;

    ServiceTreeItemData *data = (ServiceTreeItemData *)tree_ctrl_1->GetItemData(item);
    if (data == NULL || data->GetNodeType() != TREE_NODE_SERVICE)
        return;

    const struct tansu_cp_service *cp_service = data->GetService();
    if (cp_service == NULL)
        return;

    struct tdom_elem *action = tansu_scpd_get_action(cp_service->scpd, str.ToUTF8());
    action = tdom_get_elem(action, "argumentList", TANSU_NS_UPNP_SERVICE, 0);
    if (action == NULL)
        return;

    int n = tdom_count_elem(action, "argument", TANSU_NS_UPNP_SERVICE);
    int i;

    for (i = 0; i < n; i++) {
        struct tdom_elem *argument = tdom_get_elem(action, "argument", TANSU_NS_UPNP_SERVICE, i);
        const char *tmp;
        tmp = tdom_get_value(tdom_get_elem(argument, "name", TANSU_NS_UPNP_SERVICE, 0));
        if (tmp == NULL)
            continue;

        long m = list_ctrl_argument->InsertItem(list_ctrl_argument->GetItemCount(),
                                                wxString::FromUTF8(tmp));

        tmp = tdom_get_value(tdom_get_elem(argument, "direction", TANSU_NS_UPNP_SERVICE, 0));
        if (tmp)
            list_ctrl_argument->SetItem(m, 1, wxString::FromUTF8(tmp));

        if (tdom_get_elem(argument, "retval", TANSU_NS_UPNP_SERVICE, 0))
            list_ctrl_argument->SetItem(m, 2, _("Y"));

        tmp = tdom_get_value(tdom_get_elem(argument,
                                           "relatedStateVariable",
                                           TANSU_NS_UPNP_SERVICE,
                                           0));
        if (tmp)
            list_ctrl_argument->SetItem(m, 3, wxString::FromUTF8(tmp));
    }

    button_exec->Enable(true);
    button_exec_option->Enable(true);
}


void UpnpFrame::OnActionExecButton(wxCommandEvent &event)
{
    event.Skip();

    long idx = GetSelectedListIndex(*list_ctrl_action);
    if (idx < 0)
        return;

    wxString name = list_ctrl_action->GetItemText(idx);

    const struct tansu_cp_device *cp_device = GetCurrentDevice();
    if (cp_device == NULL)
        return;

    const struct tansu_cp_service *cp_service = GetCurrentService();
    if (cp_service == NULL)
        return;

    wxFrame *frame;
    frame = new SoapFrame(evbase, name, 0, cp_device, cp_service,
                          this, wxID_ANY, wxEmptyString);
    if (frame)
        frame->Show();
}


void UpnpFrame::OnActionExecOptionButton(wxCommandEvent &event)
{
    event.Skip();
    printf("Event handler (UpnpFrame::OnActionExecOptionButton) not implemented yet\n");
    fflush(stdout);
    // TODO:
}


void UpnpFrame::OnStateVariableDeselected(wxListEvent &event)
{
    event.Skip();
    list_ctrl_allowed_value->DeleteAllItems();
    text_ctrl_minimum->SetValue(wxEmptyString);
    text_ctrl_maximum->SetValue(wxEmptyString);
    text_ctrl_step->SetValue(wxEmptyString);
}


void UpnpFrame::OnStateVariableSelected(wxListEvent &event)
{
    event.Skip();

    list_ctrl_allowed_value->DeleteAllItems();
    text_ctrl_minimum->SetValue(wxEmptyString);
    text_ctrl_maximum->SetValue(wxEmptyString);
    text_ctrl_step->SetValue(wxEmptyString);

    long idx = event.GetIndex();
    wxString str = list_ctrl_state_variable->GetItemText(idx);

    wxTreeItemId item = tree_ctrl_1->GetSelection();
    if (item.IsOk() == false)
        return;

    ServiceTreeItemData *data = (ServiceTreeItemData *)tree_ctrl_1->GetItemData(item);
    if (data == NULL || data->GetNodeType() != TREE_NODE_SERVICE)
        return;

    const struct tansu_cp_service *cp_service = data->GetService();
    if (cp_service == NULL)
        return;

    int i;
    int n;
    const char *tmp;
    struct tdom_elem *sv;
    struct tdom_elem *values;
    sv = tansu_scpd_get_state_variable(cp_service->scpd, str.ToUTF8());
    values = tdom_get_elem(sv, "allowedValueList", TANSU_NS_UPNP_SERVICE, 0);
    if (values == NULL)
        goto next;

    n = tdom_count_elem(values, "allowedValue", TANSU_NS_UPNP_SERVICE);
    for (i = 0; i < n; i++) {
        tmp = tdom_get_value(tdom_get_elem(values, "allowedValue", TANSU_NS_UPNP_SERVICE, i));
        if (tmp)
            list_ctrl_allowed_value->InsertItem(list_ctrl_allowed_value->GetItemCount(),
                                                wxString::FromUTF8(tmp));
    }

next:

    values = tdom_get_elem(sv, "allowedValueRange", TANSU_NS_UPNP_SERVICE, 0);
    if (values) {
        struct {
            const char *name;
            wxTextCtrl *ctrl;
        } list[] = {
            {"minimum", text_ctrl_minimum},
            {"maximum", text_ctrl_maximum},
            {"step",    text_ctrl_step},
        };

        for (i = 0; i < (int)TNUMELEM(list); i++) {
            tmp = tdom_get_value(tdom_get_elem(values, list[i].name, TANSU_NS_UPNP_SERVICE, 0));
            if (tmp)
                list[i].ctrl->SetValue(wxString::FromUTF8(tmp));
        }
    }
}


void UpnpFrame::OnSubscribeButton(wxCommandEvent &event)
{
    event.Skip();

    const struct tansu_cp_device *cp_device = GetCurrentDevice();
    if (cp_device == NULL)
        return;

    const struct tansu_cp_service *cp_service = GetCurrentService();
    if (cp_service == NULL)
        return;

    wxFrame *frame;
    frame = new GenaFrame(evbase, cp_device, cp_service, this, wxID_ANY, wxEmptyString);
    if (frame)
        frame->Show();
}


// wxGlade: add UpnpFrame event handlers


void UpnpFrame::set_properties()
{
    // begin wxGlade: UpnpFrame::set_properties
    SetTitle(wxT("UPnP Client"));
    SetSize(wxSize(800, 600));
    int frame_1_statusbar_widths[] = { 120, -1 };
    frame_1_statusbar->SetStatusWidths(2, frame_1_statusbar_widths);
    const wxString frame_1_statusbar_fields[] = {
        wxT("device:"),
        wxEmptyString
    };
    for(int i = 0; i < frame_1_statusbar->GetFieldsCount(); ++i) {
        frame_1_statusbar->SetStatusText(frame_1_statusbar_fields[i], i);
    }
    combo_box_st->SetMinSize(wxSize(50,10));
    combo_box_st->SetSelection(0);
    combo_box_filter->SetMinSize(wxSize(50,10));
    combo_box_filter->SetSelection(0);
    notebook_1->Hide();
    button_exec->Enable(false);
    button_exec_option->Enable(false);
    notebook_2->Hide();
    // end wxGlade

    list_ctrl_ssdp->InsertColumn(0, _("UDN"));
    list_ctrl_ssdp->SetColumnWidth(0, 50);
    list_ctrl_ssdp->InsertColumn(1, _("IP Address"));
    list_ctrl_ssdp->SetColumnWidth(1, 110);
    list_ctrl_ssdp->InsertColumn(2, _("State"));
    list_ctrl_ssdp->SetColumnWidth(2, 60);
    list_ctrl_ssdp->InsertColumn(3, _("Status"));
    list_ctrl_ssdp->SetColumnWidth(3, 80);
    list_ctrl_ssdp->InsertColumn(4, _("Timestamp"));
    list_ctrl_ssdp->SetColumnWidth(4, 150);
    list_ctrl_ssdp->InsertColumn(5, _("Location"));
    list_ctrl_ssdp->SetColumnWidth(5, 250);

    list_ctrl_device->InsertColumn(0, _("Name"));
    list_ctrl_device->SetColumnWidth(0, 150);
    list_ctrl_device->InsertColumn(1, _("Value"));
    list_ctrl_device->SetColumnWidth(1, 400);

    list_ctrl_icon->InsertColumn(0, _("mimetype"));
    list_ctrl_icon->SetColumnWidth(0, 120);
    list_ctrl_icon->InsertColumn(1, _("width"), wxLIST_FORMAT_RIGHT);
    list_ctrl_icon->SetColumnWidth(1, 50);
    list_ctrl_icon->InsertColumn(2, _("height"), wxLIST_FORMAT_RIGHT);
    list_ctrl_icon->SetColumnWidth(2, 50);
    list_ctrl_icon->InsertColumn(3, _("depth"), wxLIST_FORMAT_RIGHT);
    list_ctrl_icon->SetColumnWidth(3, 50);
    list_ctrl_icon->InsertColumn(4, _("url"));
    list_ctrl_icon->SetColumnWidth(4, 200);

#define SERVICE_COLUMN_WIDTH    300
    list_ctrl_service->InsertColumn(0, _("serviceType"));
    list_ctrl_service->SetColumnWidth(0, SERVICE_COLUMN_WIDTH);
    list_ctrl_service->InsertColumn(1, _("serviceId"));
    list_ctrl_service->SetColumnWidth(1, SERVICE_COLUMN_WIDTH);
    list_ctrl_service->InsertColumn(2, _("SCPDURL"));
    list_ctrl_service->SetColumnWidth(2, SERVICE_COLUMN_WIDTH);
    list_ctrl_service->InsertColumn(3, _("controlURL"));
    list_ctrl_service->SetColumnWidth(3, SERVICE_COLUMN_WIDTH);
    list_ctrl_service->InsertColumn(4, _("eventSubURL"));
    list_ctrl_service->SetColumnWidth(4, SERVICE_COLUMN_WIDTH);

    list_ctrl_action->InsertColumn(0, _("Action"));
    list_ctrl_action->SetColumnWidth(0, 500);

    list_ctrl_argument->InsertColumn(0, _("name"));
    list_ctrl_argument->SetColumnWidth(0, 200);
    list_ctrl_argument->InsertColumn(1, _("direction"), wxLIST_FORMAT_CENTER);
    list_ctrl_argument->SetColumnWidth(1, 70);
    list_ctrl_argument->InsertColumn(2, _("retval"), wxLIST_FORMAT_CENTER);
    list_ctrl_argument->SetColumnWidth(2, 60);
    list_ctrl_argument->InsertColumn(3, _("relatedStateVariable"));
    list_ctrl_argument->SetColumnWidth(3, 250);

    list_ctrl_state_variable->InsertColumn(0, _("name"));
    list_ctrl_state_variable->SetColumnWidth(0, 200);
    list_ctrl_state_variable->InsertColumn(1, _("sendEvents"), wxLIST_FORMAT_CENTER);
    list_ctrl_state_variable->SetColumnWidth(1, 80);
    list_ctrl_state_variable->InsertColumn(2, _("dataType"));
    list_ctrl_state_variable->SetColumnWidth(2, 80);
    list_ctrl_state_variable->InsertColumn(3, _("defaultValue"));
    list_ctrl_state_variable->SetColumnWidth(3, 200);

    list_ctrl_allowed_value->InsertColumn(0, _("allowedValue"));
    list_ctrl_allowed_value->SetColumnWidth(0, 500);
}


void UpnpFrame::do_layout()
{
    // begin wxGlade: UpnpFrame::do_layout
    wxFlexGridSizer* sizer_1 = new wxFlexGridSizer(2, 1, 0, 0);
    wxFlexGridSizer* grid_sizer_3 = new wxFlexGridSizer(1, 3, 0, 0);
    wxFlexGridSizer* grid_sizer_13 = new wxFlexGridSizer(3, 3, 0, 0);
    wxGridSizer* sizer_4 = new wxGridSizer(1, 1, 0, 0);
    wxFlexGridSizer* grid_sizer_10 = new wxFlexGridSizer(5, 1, 0, 0);
    wxFlexGridSizer* grid_sizer_11 = new wxFlexGridSizer(1, 6, 0, 0);
    wxFlexGridSizer* grid_sizer_12 = new wxFlexGridSizer(1, 2, 0, 0);
    wxFlexGridSizer* grid_sizer_8 = new wxFlexGridSizer(5, 1, 0, 0);
    wxFlexGridSizer* sizer_5 = new wxFlexGridSizer(1, 4, 0, 0);
    wxFlexGridSizer* grid_sizer_9 = new wxFlexGridSizer(1, 4, 0, 0);
    wxGridSizer* sizer_3 = new wxGridSizer(1, 1, 0, 0);
    wxFlexGridSizer* grid_sizer_4 = new wxFlexGridSizer(9, 1, 0, 0);
    wxFlexGridSizer* grid_sizer_7 = new wxFlexGridSizer(1, 2, 0, 0);
    wxFlexGridSizer* grid_sizer_6 = new wxFlexGridSizer(1, 2, 0, 0);
    wxFlexGridSizer* grid_sizer_5 = new wxFlexGridSizer(1, 4, 0, 0);
    wxFlexGridSizer* grid_sizer_14 = new wxFlexGridSizer(1, 2, 0, 0);
    wxFlexGridSizer* grid_sizer_2 = new wxFlexGridSizer(2, 1, 0, 0);
    wxBoxSizer* sizer_2 = new wxBoxSizer(wxHORIZONTAL);
    wxFlexGridSizer* grid_sizer_1 = new wxFlexGridSizer(1, 4, 0, 0);
    grid_sizer_1->Add(combo_box_st, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_1->Add(button_search, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_1->Add(combo_box_filter, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_1->Add(button_start, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_1->AddGrowableCol(0);
    grid_sizer_1->AddGrowableCol(2);
    sizer_1->Add(grid_sizer_1, 1, wxEXPAND, 0);
    notebook_tree->AddPage(tree_ctrl_1, wxT("Active Devices"));
    notebook_tree->AddPage(list_ctrl_ssdp, wxT("SSDP"));
    grid_sizer_2->Add(notebook_tree, 1, wxEXPAND, 0);
    sizer_2->Add(button_collapse, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    sizer_2->Add(button_expand, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    sizer_2->Add(button_clear, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_2->Add(sizer_2, 1, wxEXPAND|wxADJUST_MINSIZE, 0);
    panel_1->SetSizer(grid_sizer_2);
    grid_sizer_2->AddGrowableRow(0);
    grid_sizer_2->AddGrowableCol(0);
    grid_sizer_14->Add(label_15, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_14->Add(text_ctrl_location, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_14->AddGrowableCol(1);
    grid_sizer_4->Add(grid_sizer_14, 1, wxEXPAND, 0);
    grid_sizer_5->Add(label_1, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_5->Add(text_ctrl_device_major, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_5->Add(label_2, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_5->Add(text_ctrl_device_minor, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_4->Add(grid_sizer_5, 1, wxEXPAND, 0);
    grid_sizer_6->Add(label_3, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_6->Add(text_ctrl_url_base, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_6->AddGrowableCol(1);
    grid_sizer_4->Add(grid_sizer_6, 1, wxEXPAND, 0);
    grid_sizer_4->Add(list_ctrl_device, 1, wxEXPAND, 0);
    grid_sizer_4->Add(label_4, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_4->Add(list_ctrl_icon, 1, wxEXPAND, 0);
    grid_sizer_4->Add(label_5, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_4->Add(list_ctrl_service, 1, wxEXPAND, 0);
    grid_sizer_7->Add(label_6, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_7->Add(text_ctrl_presentation, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_7->AddGrowableCol(1);
    grid_sizer_4->Add(grid_sizer_7, 1, wxEXPAND, 0);
    notebook_1_pane_1->SetSizer(grid_sizer_4);
    grid_sizer_4->AddGrowableRow(3);
    grid_sizer_4->AddGrowableRow(5);
    grid_sizer_4->AddGrowableRow(7);
    grid_sizer_4->AddGrowableCol(0);
    sizer_3->Add(text_ctrl_dd, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    notebook_1_pane_2->SetSizer(sizer_3);
    notebook_1->AddPage(notebook_1_pane_1, wxT("Device Info"));
    notebook_1->AddPage(notebook_1_pane_2, wxT("Text"));
    grid_sizer_3->Add(notebook_1, 1, wxEXPAND, 0);
    grid_sizer_9->Add(label_7, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_9->Add(text_ctrl_service_major, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_9->Add(label_8, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_9->Add(text_ctrl_service_minor, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_8->Add(grid_sizer_9, 1, wxEXPAND, 0);
    grid_sizer_8->Add(list_ctrl_action, 1, wxEXPAND, 0);
    sizer_5->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    sizer_5->Add(button_exec, 0, wxADJUST_MINSIZE, 0);
    sizer_5->Add(button_exec_option, 0, wxADJUST_MINSIZE, 0);
    sizer_5->AddGrowableCol(0);
    grid_sizer_8->Add(sizer_5, 1, wxEXPAND|wxALIGN_RIGHT, 0);
    grid_sizer_8->Add(label_9, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_8->Add(list_ctrl_argument, 1, wxEXPAND, 0);
    notebook_2_pane_1->SetSizer(grid_sizer_8);
    grid_sizer_8->AddGrowableRow(1);
    grid_sizer_8->AddGrowableRow(4);
    grid_sizer_8->AddGrowableCol(0);
    grid_sizer_10->Add(list_ctrl_state_variable, 1, wxEXPAND, 0);
    grid_sizer_12->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_12->Add(button_subscribe, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_12->AddGrowableCol(0);
    grid_sizer_10->Add(grid_sizer_12, 1, wxEXPAND, 0);
    grid_sizer_10->Add(label_10, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_10->Add(list_ctrl_allowed_value, 1, wxEXPAND, 0);
    grid_sizer_11->Add(label_11, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_11->Add(text_ctrl_minimum, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_11->Add(label_12, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_11->Add(text_ctrl_maximum, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_11->Add(label_13, 0, wxALIGN_CENTER_VERTICAL|wxADJUST_MINSIZE, 0);
    grid_sizer_11->Add(text_ctrl_step, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_10->Add(grid_sizer_11, 1, wxEXPAND, 0);
    notebook_2_pane_2->SetSizer(grid_sizer_10);
    grid_sizer_10->AddGrowableRow(0);
    grid_sizer_10->AddGrowableRow(3);
    grid_sizer_10->AddGrowableCol(0);
    sizer_4->Add(text_ctrl_scpd, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    notebook_2_pane_3->SetSizer(sizer_4);
    notebook_2->AddPage(notebook_2_pane_1, wxT("Actions"));
    notebook_2->AddPage(notebook_2_pane_2, wxT("State Variables"));
    notebook_2->AddPage(notebook_2_pane_3, wxT("Text"));
    grid_sizer_3->Add(notebook_2, 1, wxEXPAND, 0);
    grid_sizer_13->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_13->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_13->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_13->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_13->Add(label_14, 0, wxADJUST_MINSIZE, 0);
    grid_sizer_13->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_13->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_13->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    grid_sizer_13->Add(20, 20, 0, wxEXPAND|wxADJUST_MINSIZE, 0);
    panel_3->SetSizer(grid_sizer_13);
    grid_sizer_13->AddGrowableRow(0);
    grid_sizer_13->AddGrowableRow(2);
    grid_sizer_13->AddGrowableCol(0);
    grid_sizer_13->AddGrowableCol(2);
    grid_sizer_3->Add(panel_3, 1, wxEXPAND, 0);
    panel_2->SetSizer(grid_sizer_3);
    grid_sizer_3->AddGrowableRow(0);
    grid_sizer_3->AddGrowableCol(0);
    grid_sizer_3->AddGrowableCol(1);
    grid_sizer_3->AddGrowableCol(2);
    window_1->SplitVertically(panel_1, panel_2, 380);
    sizer_1->Add(window_1, 1, wxEXPAND|wxADJUST_MINSIZE, 0);
    SetSizer(sizer_1);
    sizer_1->AddGrowableRow(1);
    sizer_1->AddGrowableCol(0);
    Layout();
    // end wxGlade

    sizer = grid_sizer_3;
}

static const char *GetStateString(enum tansu_ssdp_state state)
{
    switch (state) {
    case TANSU_SSDP_STATE_CREATED:
        return "Created";
    case TANSU_SSDP_STATE_ONLINE:
        return "Online";
    case TANSU_SSDP_STATE_OFFLINE:
        return "Offline";
    default:
        return "Unknown";
    }
}

static const struct {
    int status;
    const wxChar *string;
    const wxColour *color;
} status_list[] = {
    {-2,    _(""),             wxBLUE},
    {-1,    _("Checking"),     wxBLACK},
    {0,     _("Failed"),       wxLIGHT_GREY},
    {403,   _("Forbidden"),    wxRED},
};

static wxString GetStatusString(int status)
{
    unsigned int i;
    for (i = 0; i < TNUMELEM(status_list); i++) {
        if (status == status_list[i].status)
            return status_list[i].string;
    }

    return wxString::Format(_("%d"), status);
}

static const wxColour& GetStatusColor(int status)
{
    unsigned int i;
    for (i = 0; i < TNUMELEM(status_list); i++) {
        if (status == status_list[i].status)
            return *status_list[i].color;
    }

    return *wxBLACK;
}

void UpnpFrame::HandlePacket(const struct tansu_ssdp_packet *packet)
{
    struct tansu_cp_discovery *discovery;
    struct tansu_keyval *type;
    struct tansu_keyval *usn;
    struct tansu_keyval *location;
    struct tansu_keyval *_nts;
    wxString udn_str = wxEmptyString;
    wxString location_str = wxEmptyString;
    wxString type_str = wxEmptyString;
    enum tansu_ssdp_nts nts = TANSU_NTS_ALIVE;

    if (packet->kind == TANSU_SSDP_RESPONSE) {
        if (packet->res.status != 200)
            return;
        if ((usn = tansu_keyvals_get(&packet->headers, "USN")) == NULL)
            return;
        if ((location = tansu_keyvals_get(&packet->headers, "Location")) == NULL)
            return;
        if (strncmp(usn->value, "uuid:", 5) != 0)
            return;

        location_str = wxString::FromAscii(location->value);
        udn_str = wxString(usn->value, wxConvUTF8);
        const char *endp = strstr(usn->value, "::");
        if (endp) {
            size_t len = endp - usn->value;
            udn_str.Truncate(len);
            type_str = wxString::FromAscii(&endp[2]);
        }
        goto next;
    }

    if (strcmp(packet->req.method, "NOTIFY") == 0) {
        if ((type = tansu_keyvals_get(&packet->headers, "NT")) == NULL)
            return;
        if ((usn = tansu_keyvals_get(&packet->headers, "USN")) == NULL)
            return;
        if (strncmp(usn->value, "uuid:", 5) != 0)
            return;
        if ((location = tansu_keyvals_get(&packet->headers, "Location")) == NULL)
            return;
        if ((_nts = tansu_keyvals_get(&packet->headers, "NTS")) == NULL)
            return;

        // check notification sub type
        if (strcmp(_nts->value, TANSU_SSDP_NTS_ALIVE) == 0)
            nts = TANSU_NTS_ALIVE;
        else if (strcmp(_nts->value, TANSU_SSDP_NTS_BYEBYE) == 0)
            nts = TANSU_NTS_BYEBYE;
        else
            return;

        type_str = wxString::FromAscii(type->value);
        if (filter.Len() > 0)
            if (type_str.Mid(0, filter.Len()) != filter)
                return;
        location_str = wxString::FromAscii(location->value);
        udn_str = wxString::FromAscii(usn->value);
        const char *endp = strstr(usn->value, "::");
        if (endp) {
            size_t len = endp - usn->value;
            udn_str.Truncate(len);
        }
    } else {
        return;
    }

next:

    // check device tree
    wxTreeItemId id = FindDevice(&type_str, udn_str);
    if (id.IsOk()) {
        // TODO: update and re-acquisition
        UpdateDevice(nts, packet);
        return;
    }

    // check discovery list
    discovery = tansu_cp_discoveries_get(&discoveries, udn_str.ToUTF8());
    if (discovery) {
        UpdateDiscovery(discovery, nts, packet);
        return;
    }

    discovery = tansu_cp_discovery_new(evbase, udn_str.ToUTF8(), packet);
    if (discovery == NULL)
        return;
    time(&discovery->timestamp);

    char *tmp = tansu_socket_getaddress(&packet->remote);
    wxString addr(tmp, wxConvUTF8);
    free(tmp);

    STAILQ_INSERT_TAIL(&discoveries, discovery, next);
    discovery->state = TANSU_SSDP_STATE_ONLINE;

    const char *path = tansu_url_getpath(location->value);
    struct get_context *context = new struct get_context;
    context->frame = this;
    context->discovery = discovery;
    context->sid = NULL;

    tansu_status_t ret;
    ret = tansu_http_get(discovery->conn, path, NULL, dd_handler, context);

    if (ret == TANSU_SUCCESS) {
        discovery->jobs += 1;
        int n = list_ctrl_ssdp->GetItemCount();
        long m = list_ctrl_ssdp->InsertItem(n, udn_str);
        UpdateDiscoveryList(m, discovery);
    } else {
        delete context;
        tansu_cp_discovery_delete(discovery);
    }
}

void UpnpFrame::ExecuteMSearch()
{
    if (!button_search->IsEnabled())
        return;

    free(req->st);
    wxString st = combo_box_st->GetValue();
    req->st = strdup(st.ToAscii());
    if (req->st == NULL) {
        frame_1_statusbar->SetStatusText(_("internal error"), 1);
        return;
    }

    if (combo_box_st->FindString(st) == wxNOT_FOUND)
        combo_box_st->Append(st);

    num_responses = 0;
    num_valid = 0;
    tansu_status_t status = tansu_ssdp_request_msearch(evbase, req, NULL);
    if (status == TANSU_SUCCESS) {
        button_search->Enable(false);
        frame_1_statusbar->SetStatusText(_("searching..."), 1);
    }
}

void UpnpFrame::MSearchFinished(struct tansu_ssdp_request *req)
{
    button_search->Enable(true);
    wxString str = wxString::Format(_("done (%d %s)  /  "),
                                    num_valid,
                                    num_valid > 1 ? _("responses") : _("response"));
    str += wxString::FromAscii(req->st);
    frame_1_statusbar->SetStatusText(str, 1);
}

void UpnpFrame::DDHandler(struct evhttp_request *req, struct tansu_cp_discovery *discovery)
{
    discovery->jobs -= 1;

    wxString udn(discovery->udn, wxConvUTF8);
    long n = list_ctrl_ssdp->FindItem(0, udn);

#if 0
    if (req == NULL || req->response_code != HTTP_OK) {
        if (discovery->jobs <= 0) {
            evhttp_connection_free(discovery->conn);
            discovery->conn = NULL;
        }
    }
#endif

    if (req == NULL) {
        discovery->last_status = 0;
        UpdateDiscoveryList(n, discovery);
        return;
    }

    discovery->last_status = req->response_code;

    if (req->response_code == 403) {
        UpdateDiscoveryList(n, discovery);
        return;
    }
    if (req->response_code == HTTP_OK) {
        struct tdom_doc *dd = tansu_xml_parse_evbuffer(req->input_buffer);
        if (dd) {
            struct tansu_cp_device *device = tansu_cp_device_new(discovery->udn,
                                                                 discovery->location,
                                                                 dd);
            if (device) {
                DeviceTreeItemData *data = new DeviceTreeItemData(device);
                if (data)
                    SetTreeItem(discovery, data);
                else
                    tansu_cp_device_delete(device);
            } else {
                tdom_doc_delete(dd);
            }
        }

        // any other jobs?
        if (discovery->jobs <= 0) {
            STAILQ_REMOVE(&discoveries, discovery, tansu_cp_discovery, next);
            tansu_cp_discovery_delete(discovery);
            if (n != -1)
                list_ctrl_ssdp->DeleteItem(n);
        }
    } else {
        discovery->last_status = 0;
        UpdateDiscoveryList(n, discovery);
        return;
    }
}

void
UpnpFrame::SCPDHandler(struct evhttp_request *req,
                       struct tansu_cp_discovery *discovery,
                       const char *sid)
{
    discovery->jobs -= 1;

    wxString udn(discovery->udn, wxConvUTF8);
    long n = list_ctrl_ssdp->FindItem(0, udn);

    do {
        if (req == NULL || req->response_code != HTTP_OK)
            break;

        // get tree node
        wxTreeItemId id = FindDevice(NULL, wxString::FromAscii(discovery->udn));
        if (id.IsOk() == false)
            break;

        // find service node
        wxTreeItemId item = FindTreeItem(id, wxString::FromAscii(sid));
        if (id.IsOk() == false)
            break;
        ServiceTreeItemData *sdata = (ServiceTreeItemData *)tree_ctrl_1->GetItemData(item);
        if (sdata == NULL)
            break;

        // get service type
        DeviceTreeItemData *data = (DeviceTreeItemData *)tree_ctrl_1->GetItemData(id);
        if (data == NULL)
            break;
        const struct tansu_cp_device *device = data->GetDevice();
        struct tdom_elem *elem = device->embedded ? device->embedded : device->rootdevice;
        const char *type = tansu_dd_get_service_type(elem, sid);
        if (type == NULL)
            break;

        // create scpd
        struct tdom_doc *scpd = tansu_xml_parse_evbuffer(req->input_buffer);
        if (scpd == NULL)
            break;

        // create service
        struct tansu_cp_service *service = tansu_cp_service_new(type, sid, scpd);
        if (service == NULL) {
            tdom_doc_delete(scpd);
            break;
        }

        // set service
        sdata->SetService(service);
        tree_ctrl_1->SetItemTextColour(item, *wxBLACK);
        if (tree_ctrl_1->GetSelection() == item)
            SetServiceInfo(sdata);

    } while (0);

    if (discovery->jobs <= 0) {
        STAILQ_REMOVE(&discoveries, discovery, tansu_cp_discovery, next);
        tansu_cp_discovery_delete(discovery);
        if (n != -1)
            list_ctrl_ssdp->DeleteItem(n);
    }
}

wxTreeItemId
UpnpFrame::CreateTreeNode(const wxTreeItemId parent, const wxString& text)
{
    if (combo_box_st->FindString(text) == wxNOT_FOUND)
        combo_box_st->Append(text);

    wxTreeItemId id = tree_ctrl_1->AppendItem(parent, text);
    TextTreeItemData *data = new TextTreeItemData(text);
    tree_ctrl_1->SetItemData(id, data);

    return id;
}

wxTreeItemId
UpnpFrame::GetTreeNode(const wxTreeItemId parent, const wxString& text, bool create)
{
    wxTreeItemIdValue cookie;
    wxTreeItemId id = tree_ctrl_1->GetFirstChild(parent, cookie);

    while (id.IsOk()) {
        TextTreeItemData *data = (TextTreeItemData *)tree_ctrl_1->GetItemData(id);
        if (data->GetText() == text)
            return id;
        id = tree_ctrl_1->GetNextSibling(id);
    }

    return create ? CreateTreeNode(parent, text) : id;
}

wxTreeItemId
UpnpFrame::FindDevice(const wxString *type, const wxString& udn)
{
    wxTreeItemId id;

    if (type && type->Len() > 0) {
        if (type->StartsWith(_("urn:")) == false)
            return id;
        id = GetTreeNode(root, *type);
        if (id.IsOk())
            return GetDeviceTreeNode(id, udn);
    }

    wxTreeItemIdValue cookie;
    wxTreeItemId tmp = tree_ctrl_1->GetFirstChild(root, cookie);
    DeviceTreeItemData *data;

    while (tmp.IsOk()) {
        id = GetDeviceTreeNode(tmp, udn);
        if (id.IsOk())
            return id;
        tmp = tree_ctrl_1->GetNextSibling(tmp);
    }

    return tmp;
}

wxTreeItemId
UpnpFrame::GetDeviceTreeNode(const wxTreeItemId parent, const wxString& udn)
{
    wxTreeItemIdValue cookie;
    wxTreeItemId id = tree_ctrl_1->GetFirstChild(parent, cookie);
    DeviceTreeItemData *data;

    while (id.IsOk() == true) {
        if ((data = (DeviceTreeItemData *)tree_ctrl_1->GetItemData(id)) == NULL)
            continue;
        if (udn == wxString::FromAscii(data->GetDevice()->udn))
            return id;
        id = tree_ctrl_1->GetNextSibling(id);
    }

    return id;
}

wxTreeItemId
UpnpFrame::FindTreeItem(const wxTreeItemId parent, const wxString& text)
{
    wxTreeItemIdValue cookie;
    wxTreeItemId id = tree_ctrl_1->GetFirstChild(parent, cookie);
    DeviceTreeItemData *data;

    while (id.IsOk() == true) {
        if (tree_ctrl_1->GetItemText(id) == text)
            return id;
        id = tree_ctrl_1->GetNextSibling(id);
    }

    return id;
}

wxString UpnpFrame::GetTime(const time_t *t)
{
    struct tm tm;

    if (localtime_r(t, &tm) == NULL)
        return wxEmptyString;

    return wxString::Format(_("%04d-%02d-%02d %02d:%02d:%02d"),
                            tm.tm_year + 1900,
                            tm.tm_mon,
                            tm.tm_mday,
                            tm.tm_hour,
                            tm.tm_min,
                            tm.tm_sec);
}

void
UpnpFrame::SetTreeItem(struct tansu_cp_discovery *discovery,
                       DeviceTreeItemData *data)
{
    struct tdom_elem *elem;
    const struct tansu_cp_device *device = data->GetDevice();
    elem = device->embedded ? device->embedded : device->rootdevice;

    do {
        if (elem == NULL)
            break;

        const char *deviceType;
        deviceType = tdom_get_value(tdom_get_elem(elem,
                                    "deviceType",
                                    TANSU_NS_UPNP_DEVICE,
                                    0));
        if (deviceType == NULL)
            break;

        const char *friendlyName;
        friendlyName = tdom_get_value(tdom_get_elem(elem,
                                      "friendlyName",
                                      TANSU_NS_UPNP_DEVICE,
                                      0));
        if (friendlyName == NULL)
            break;
        wxString fn = wxString::FromUTF8(friendlyName);

        wxTreeItemId type = GetTreeNode(root, wxString::FromUTF8(deviceType), true);
        wxTreeItemId dev = GetDeviceTreeNode(type, wxString::FromUTF8(device->udn));
        if (dev.IsOk() == false) {
            // append
            dev = tree_ctrl_1->AppendItem(type, fn);
            tree_ctrl_1->SetItemData(dev, data);
            // update device count
            UpdateTypeNodeText(type);
            UpdateServices(dev, data, discovery);
        } else {
            // update
            tree_ctrl_1->SetItemText(dev, fn);
            wxTreeItemData *tmp = tree_ctrl_1->GetItemData(dev);
            tree_ctrl_1->SetItemData(dev, data);
            delete tmp;
            UpdateServices(dev, data, discovery);
        }

        return;

    } while (0);

    delete data;
}

void
UpnpFrame::UpdateServices(wxTreeItemId& id,
                          DeviceTreeItemData *data,
                          struct tansu_cp_discovery *discovery)
{
    struct tdom_elem *elem;
    const struct tansu_cp_device *device = data->GetDevice();
    elem = device->embedded ? device->embedded : device->rootdevice;

    if (data == NULL) {
        tree_ctrl_1->DeleteChildren(id);
        return;
    }

    const char *base = tansu_dd_get_elem_value(device->dd->root, "URLBase");

    struct tdom_elem *serviceList;
    struct tdom_elem *service;

    serviceList = tansu_dd_get_elem(elem, "serviceList", 0);
    if (serviceList == NULL) {
        tree_ctrl_1->DeleteChildren(id);
        return;
    }

    int n = tdom_count_elem(serviceList, "service", TANSU_NS_UPNP_DEVICE);

    int i;
    for (i = 0; i < n; i++) {
        service = tansu_dd_get_elem(serviceList, "service", i);
        if (service == NULL)
            continue;

        const char *serviceId;
        if ((serviceId = tansu_dd_get_elem_value(service, "serviceId")) == NULL)
            continue;

        wxString text(serviceId, wxConvUTF8);
        wxTreeItemId item = FindTreeItem(id, text);
        ServiceTreeItemData *sdata;

        // add service node
        if (item.IsOk() == false) {
            sdata = new ServiceTreeItemData();
            if (sdata == NULL)
                continue;
            item = tree_ctrl_1->AppendItem(id, text);
            if (item.IsOk() == false) {
                delete sdata;
                continue;
            }
            tree_ctrl_1->SetItemData(item, sdata);
            tree_ctrl_1->SetItemTextColour(item, *wxLIGHT_GREY);
        } else {
            sdata = (ServiceTreeItemData *)tree_ctrl_1->GetItemData(item);
        }

        if (sdata == NULL)
            continue;

        if (sdata->GetService() != NULL)
            continue;

        const char *url;
        if ((url = tansu_dd_get_elem_value(service, "SCPDURL")) == NULL)
            continue;

        wxString tmp = wxEmptyString;
        char *abs = tansu_url_get_abs(discovery->location, base, url);
        if (abs) {
            if (tansu_url_parse(abs, NULL, NULL, NULL, NULL, NULL, &url) == TANSU_SUCCESS)
                tmp = wxString::FromAscii(url);
            free(abs);
        }

        struct get_context *context = new struct get_context;
        context->frame = this;
        context->discovery = discovery;
        context->sid = strdup(serviceId);

        tansu_status_t ret;
        ret = tansu_http_get(discovery->conn, tmp.ToAscii(), NULL, scpd_handler, context);
        if (ret == TANSU_SUCCESS) {
            discovery->jobs += 1;
        } else {
            free(context->sid);
            delete context;
        }
    }
}

void UpnpFrame::SetActivePanel(enum UpnpPanelId id)
{
    notebook_1->Hide();
    notebook_2->Hide();
    panel_3->Hide();

    switch (id) {
    case PANEL_DEVICE:
        notebook_1->Show();
        break;
    case PANEL_SERVICE:
        notebook_2->Show();
        break;
    case PANEL_NONE:
    default:
        panel_3->Show();
        break;
    }

    sizer->Layout();
}

void UpnpFrame::UpdateDiscovery(struct tansu_cp_discovery *discovery,
                                enum tansu_ssdp_nts nts,
                                const struct tansu_ssdp_packet *packet)
{
    wxString udn(discovery->udn, wxConvUTF8);

    if (nts = TANSU_NTS_ALIVE) {
        discovery->state = TANSU_SSDP_STATE_ONLINE;
    } else if (nts = TANSU_NTS_BYEBYE) {
        discovery->state = TANSU_SSDP_STATE_OFFLINE;
        discovery->last_status = -2;
    }

    //if (discovery->state == TANSU_SSDP_STATE_ALIVE) {
    if (discovery->last_status == -1) {
        time(&discovery->timestamp);
        long n = list_ctrl_ssdp->FindItem(0, udn);
        if (n != -1)
            UpdateDiscoveryList(n, discovery);
    }
    // TODO: update state
}

void UpnpFrame::UpdateDiscoveryList(long n, struct tansu_cp_discovery *discovery)
{
    char *tmp = tansu_socket_getaddress(&discovery->packet->remote);
    wxString addr(tmp, wxConvUTF8);
    free(tmp);

    list_ctrl_ssdp->SetItem(n, 1, addr);
    list_ctrl_ssdp->SetItem(n, 2, wxString::FromUTF8(GetStateString(discovery->state)));
    list_ctrl_ssdp->SetItem(n, 3, GetStatusString(discovery->last_status));
    list_ctrl_ssdp->SetItemTextColour(n, GetStatusColor(discovery->last_status));
    list_ctrl_ssdp->SetItem(n, 4, GetTime(&discovery->timestamp));

    struct tansu_keyval *location;
    if ((location = tansu_keyvals_get(&discovery->packet->headers, "Location")))
        list_ctrl_ssdp->SetItem(n, 5, wxString::FromAscii(location->value));
}

void UpnpFrame::UpdateTypeNodeText(wxTreeItemId& id)
{
    TextTreeItemData *data = (TextTreeItemData *)tree_ctrl_1->GetItemData(id);
    if (data == NULL)
        return;
    wxString text;
    text += wxString::Format(_("(%d) "), tree_ctrl_1->GetChildrenCount(id, false));
    text += data->GetText();
    tree_ctrl_1->SetItemText(id, text);
}

void UpnpFrame::UpdateDevice(enum tansu_ssdp_nts nts,
                             const struct tansu_ssdp_packet *packet)
{
    // TODO: update timestamp
    // TODO: update state
}

int UpnpFrame::GetSelectedListIndex(const wxListCtrl& listCtrl)
{
    int i;
    int n = listCtrl.GetItemCount();

    for (i = 0; i < n; i++) {
        if (listCtrl.GetItemState(i, wxLIST_STATE_SELECTED))
            return i;
    }

    return -1;
}

const struct tansu_cp_device *UpnpFrame::GetCurrentDevice()
{
    wxTreeItemId item = tree_ctrl_1->GetSelection();
    if (item.IsOk() == false)
        return NULL;

    UpnpTreeItemData *data = (UpnpTreeItemData *)tree_ctrl_1->GetItemData(item);
    if (data == NULL)
        return NULL;

    if (data->GetNodeType() == TREE_NODE_SERVICE) {
        item = tree_ctrl_1->GetItemParent(item);
        if (item.IsOk() == false)
            return NULL;
        if ((data = (UpnpTreeItemData *)tree_ctrl_1->GetItemData(item)) == NULL)
            return NULL;
        if (data->GetNodeType() != TREE_NODE_DEVICE)
            return NULL;
    } else if (data->GetNodeType() != TREE_NODE_DEVICE) {
        return NULL;
    }

    return ((DeviceTreeItemData *)data)->GetDevice();
}

const struct tansu_cp_service *UpnpFrame::GetCurrentService()
{
    wxTreeItemId item = tree_ctrl_1->GetSelection();
    if (item.IsOk() == false)
        return NULL;

    UpnpTreeItemData *data = (UpnpTreeItemData *)tree_ctrl_1->GetItemData(item);
    if (data == NULL || data->GetNodeType() != TREE_NODE_SERVICE)
        return NULL;

    return ((ServiceTreeItemData *)data)->GetService();
}

